<template>
  <section class="es6-page">
  </section>
</template>
<script>
export default {
  data () {
    return {}
  },
  methods: {
  }
}

// ====== class ======
// es5
function Point5 (x, y) {
  this.x = x
  this.y = y
}
Point5.prototype.toString = function () {
  return this.x + this.y
}
const p5 = new Point5(1, 2)
console.log('构造函数：', p5, p5.toString())
// ES6
console.log('\n=====class↓↓↓\n\n')
// class的基础语法
class Point6 {
  // 定义在顶层的优势：整齐、一目了然实例的属性
  _count = 4
  static _number = 8
  static x
  static y
  #foo = 16
  // 静态块-静态属性初始化
  static {
    try {
      const obj = { x: this._number + 2, y: Point6._number *2 }
      this.x = obj.x
      this.y = obj.y
    }
    catch {
      this.x = 0
      this.y = 0
    }
  }
  constructor(x, y) {
    this.x = x
    this.y = y
  }
  static inOperator (obj) {
    // eslint-disable-next-line no-undef
    return #foo in obj // true
    // return #foo2 in obj // false
  }
  toString () {
    return this.x + this.y
  }
  get html () {
    return this.x
  }
  set html (val) {
    this.x = val
  }
  static sayName () {
    return 'handsome fei'
  }
}
const p6 = new Point6(3, 4)
const descriptor = Object.getOwnPropertyDescriptor(Point6.prototype, 'html')
console.log('class类：', p6, p6.toString(), Point6 === Point6.prototype.constructor)
console.log('原型enumerable枚举: ', Object.keys(Point5.prototype), Object.keys(Point6.prototype))
console.log('getOwnPropertyNames: ', Object.getOwnPropertyNames(Point5.prototype), Object.getOwnPropertyNames(Point6.prototype))
// console.log('属性所在: ', p6.hasOwnProperty('x'))
console.log('存取值函数设置位置：', 'get' in descriptor)
console.log('静态方法：', Point6.sayName())
// console.log('静态方法：', p6.sayName()) // TypeError: p6.sayName is not a function
class Point66 extends Point6 {}
console.log('父类的静态方法，可以被子类继承：==', Point66.sayName())
console.log('实例属性除了可以定义在constructor里面，也可以定义在class的最顶层：', p6._count)
console.log('在顶层属性前面加static，成静态属性：', Point6._number, Point6.y)
console.log('判断是否有这个私有属性：', Point6.inOperator(new Point6()))
class Point61 {
  #foo = 32
}
console.log('判断是否有这个私有属性==只对该类的实例返回true：', Point6.inOperator(new Point61()))
/**
 * 相同点：this 关键字代表【实例对象】；
 *        类的数据类型就是函数，类本身就指向构造函数；
 *        类的所有方法都定义在类的prototype属性上面，跟构造函数一致；
 *        实例的属性除了显式定义在this对象上的，其他都定义在原型prototype上；
 *        所有实例【共享】一个原型对象；
 *        class的存、取值函数都是设置在属性的Descriptor对象上的；
 * 
 * 不同点：es5原型上的方法是【可枚举】的，而es6 class内部定义的方法不可枚举；
 *        class必须用【new】调用，否则报错，es5的构造函数不用new也可以执行；
 * 
 * 注意点：严格模式；
 *        不存在提升【变量提升，这一点与es5完全不同】；
 *        name属性：class后面的关键字；
 *        this指向：bind()；
 *        静态方法：所有定义在类中的方法，实例都会继承，如果在一个方法前加上【static】关键字，就表示这个方法不会被实例继承，而是直接通过类来调用【静态方法】；
 *                 如果静态方法里面用了this，这里的this指向是类，而不是实例；
 *        静态属性：定义在类中的属性，不是实例属性；
 *        私有方法：只能在类内部访问；#
 *        私有属性：只能在类内部访问；
 */
// class的继承
class Parent {
  constructor (x, y) {
    this.x = x
    this.y = y
    console.log('Parent constructor')
  }
  toString () {
    return `x: ${this.x}, y: ${this.y};`
  }
}
class Child extends Parent {
  constructor (x, y, color) {
    super(x, y) // super用作函数调用父类的构造函数
    this.color = color
    console.log('Child constructor')
  }
  toString () {
    return this.color + ' ' + super.toString() // super用作对象调用父类原型的方法
  }
}
const child = new Child(2, 3, 'red')
console.log('child: ', child.toString())
console.log('判断一个类是否继承了另一个类：Child是否继承了Parent：', Object.getPrototypeOf(Child) === Parent)
console.log('类继承的prototype和__proto__：', Child.__proto__ === Parent, Child.prototype.__proto__ === Parent.prototype)
/**
 * 注意点：子类必须在constructor方法中调用super()，否则就会报错；这是因为子类自己的this对象，必须先通过父类的构造函数完成塑造，得到与父类同样的实例属性和方法；
 *        私有属性、方法无法继承;
 *        在子类普通方法中通过super调用父类的方法，方法内部的this指向当前的子类实例；
 * 
 * 为什么子类的构造函数一定要调用【super()】：原因就在于 ES6 的继承机制，与 ES5 完全不同。ES5 的继承机制，是先创造一个独立的子类的实例对象，然后再将父类的方法添加到这个对象上面，
 *         即“实例在前，继承在后”。ES6 的继承机制，则是先将父类的属性和方法，加到一个空的对象上面，然后再将该对象作为子类的实例，
 *         即“继承在前，实例在后”。这就是为什么 ES6 的继承必须先调用super()方法，因为这一步会生成一个继承父类的this对象，没有这一步就无法继承父类；
 */
// ====== class ======


console.log('\n=====Module↓↓↓\n\n')
// ====== Module ======
/**
 * 模块体系：Ruby的require；Python的import；CSS的@import
 * ES6之前模块方案：COMMONJS【服务器-Node.js专用】 & AMD【浏览器】
 * 注意点：require方式其实是整体读取一个对象，然后从该对象上读取属性/方法。这种加载称为“运行时加载”；
 *        import实质是从某个模块加载若干个方法，其他不读取。这种加载称为“编译时加载”或者“静态加载”
 *        ES6模块自动采用严格模式；
 * 
 *        import命令是编译阶段执行的，在代码允许之前；所以呢，【import命令是提升】的，会提升到整个模块的头部，首先执行；
 *        由于是静态执行，所以不能使用表达式和变量，这些只有在运行时才能得到结果的语法结构都是不可以的；
 * 
 *        import() 函数
 */
// export
// 详见utils/Module Module语法

// import
const dog = new Module1.Animal('dog', 3)
console.log('import Module1: ', Module1, Module1.Dog(), dog.toString())
import Module1 from '@/utils/Module'

// Module的加载实现
// script
/**
 * 默认情况下，浏览器是同步加载 JavaScript 脚本，即渲染引擎遇到<script>标签就会停下来，等到执行完脚本，再继续向下渲染。如果是外部脚本，还必须加入脚本下载的时间。
 * defer与async的区别是：defer要等到整个页面在内存中正常渲染结束（DOM 结构完全生成，以及其他脚本执行完成），才会执行；async一旦下载完，渲染引擎就会中断渲染，执行这个脚本以后，再继续渲染。
 * 一句话，defer是“渲染完再执行”，async是“下载完就执行”。另外，如果有多个defer脚本，会按照它们在页面出现的顺序加载，而多个async脚本是不能保证加载顺序的。
 */
// Node.js模块加载方法
/** CJS require() && module.exports
 * ESM import && export
 */
// ES6 模块【ESM】与 CommonJS【CJS】 模块的差异
/**
 * CommonJS 模块输出的是一个值的拷贝，ES6 模块输出的是值的引用。
 * CommonJS 模块是运行时加载，ES6 模块是编译时输出接口。
 * CommonJS 模块的require()是同步加载模块，ES6 模块的import命令是异步加载，有一个独立的模块依赖的解析阶段。
 * ES6顶层this指向undefined，CommonJS顶层this指向当前模块。
 * ES6在浏览器和服务端通用，但是模块不存在的顶层变量：arguments/require/module/exports/__filename/__dirname。
 */
// 循环加载
// CommonJS 模块的重要特性是【加载时执行】，即脚本代码在require的时候，就会全部执行。一旦出现某个模块被"循环加载"，就【只输出已经执行】的部分，还未执行的部分不会输出
// ES6 模块是动态引用，如果使用import从一个模块加载变量（即import foo from 'foo'），那些变量不会被缓存，而是成为一个指向被加载模块的引用，【需要开发者自己保证，真正取值的时候能够取到值】；
// 解决ES6循环加载变量取不到的情况===使用取值函数【函数提升】

// ====== Module ======


console.log('\n=====Promise↓↓↓\n\n')
// ====== Promise ======
/**
 * 用法
 */
// const p = new Promise((resolve, reject) => {
new Promise((resolve, reject) => {
  const val = 'hello Promise'
  const flag = true
  if (flag) {
    resolve(val)
    console.log('执行resolve或者reject并不会终结Promise的参数函数的执行！')
    console.log('但是呢，一般这两个回调函数执行了，promise的使命就完成了，剩下的应该放到回调里去执行了，所以最好在这两个回调前加上return ↓↓↓↓')
  } else {
    return reject(new Error('what`s wrong'))
  }
})
// p.then(res => {
//   console.log('p.then: ', res)
// })
/**
 * 自实现 DIY
*/
console.log('\n=====Promise实现 ↓↓↓\n')
const status = {
  P: 'pending',
  F: 'fulfilled',
  R: 'rejected'
}
const isFunction = p => typeof p === 'function'
class MyPromise {
  constructor(cb) {
    if (!isFunction(cb)) throw new Error('must accept a function as parameter')
    console.log('constructor: huhu~')
    this.state = status.P
    this.value = ''
    this.reason = ''
    // 这里为什么要队列呢，因为很有可能我没有调用resolve或者reject，状态一直是pending；
    // 我的then当然要推到队列里去，后面倒序一步步执行
    this.resolveCbQueue = []
    this.rejectCbQueue = []
    this.resolve = this._resolve.bind(this)
    this.reject = this._reject.bind(this)
    // reject()方法的作用，等同于抛出错误
    try {
      // 立马执行传入构造函数的参数【函数】，在该函数中置入我们的内置函数 resolve & reject
      cb(this.resolve, this.reject)
    } catch (e) {
      this.reason = e
    }
  }

  // 构造函数参数的内置函数 resolve() && reject() 的实现
  _resolve(val) {
    // if (this.state !== status.P) return // 只有状态是PENDING的情况下才可以执行
    // this.state = status.F
    // this.value = val
    // 改造
    const run = () => {
      if (this.state !== status.P) return
      const runFulfilled = (value) => {
        let cb = this.resolveCbQueue.shift()
        while (cb) {
          cb(value)
          cb = this.resolveCbQueue.shift()
        }
      }
      const runRejected = (error) => {
        let cb = this.rejectCbQueue.shift()
        while (cb) {
          cb(error)
          cb = this.rejectCbQueue.shift()
        }
      }

      if (val instanceof MyPromise) {
        val.then(value => {
          this.value = value
          this.state = status.F
          runFulfilled(value)
        }, err => {
          this.value = err
          this.state = status.R
          runRejected(err)
        })
      } else {
        this.state = status.F
        this.value = val
        runFulfilled(val)
      }
    }
    setTimeout(() => run(), 0)
  }
  _reject(err) {
    if (this.state !== status.P) return
    // this.state = status.R
    // this.reason = err
    // 改造
    const run = () => {
      this.state = status.R
      this.reason = err
      let cb = this.rejectCbQueue.shift()
      while (cb) {
        cb(err)
        cb = this.rejectCbQueue.shift()
      }
    }
    setTimeout(() => run(), 0)
  }

  // 类的方法
  static any (arrs) {
    console.log('any: ', arrs)
  }
  static all (arrs) {
    // 多个Promise实例，包装成一个新的Promise实例【接受一个数组】
    console.log('all: ', arrs)
  }
  static allSettled (arrs) {
    console.log('allSettled: ', arrs)
  }
  static race (arrs) {
    console.log('race: ', arrs)
  }
  /**
   * 实例状态为resolved
   * Promise.resolve('fei')
   * 等同于：new Promise(resolve => resolve('fei'))
   */
  static resolve(value) {
    if (value instanceof MyPromise) return value
    return new MyPromise(resolve => resolve(value))
  }
  /**
   * 实例状态为rejected
   * Promise.reject('it`s wrong')
   * 等同于：new Promise((resolve, reject) => reject('it`s wrong'))
   * Promise.reject()方法的参数，会原封不动地作为reject的理由，变成后续方法的参数
   */
  static reject() {
  }

  // 以下原型方法都返回一个新的Promise对象
  then (resolveCb, rejectCb) {
    console.log('Promise then: ', `state: ${this.state}`, `value: ${this.value}`, `reason: ${this.reason}`)
    const { state, value, reason } = this
    // 返回的新Promise对象状态什么时候变，变为什么状态？
    /**
     * 返回的新的 Promise 对象的状态依赖于当前 then 方法回调函数执行的情况以及返回值，例如 then 的参数是否为一个函数、回调函数执行是否出错、返回值是否为 Promise 对象
     * 所以我们定义两个方法，一个成功一个失败
     */
    return new MyPromise((resolveCbNext, rejectCbNext) => {
      // 封装一个成功函数
      const fulfilled = value => {
        try {
          if (!isFunction(resolveCb)) { // 只是一个值，直接透传下一个then
            resolveCbNext(value)
          } else {
            const result = resolveCb(value)
            if (result instanceof MyPromise) {  // 如果上一个then执行返回的是一个Promise，那么需要先把下一个then推入堆栈，等其状态改变后执行下一个回调
              result.then(resolveCbNext, rejectCbNext)
            } else { // 是一个值的话立即执行下一个then
              resolveCbNext(result)
            }
          }
        } catch (err) { // 执行失败，新的Promise对象状态为失败
          rejectCbNext(err)
        }
      }
      // 封装一个失败函数
      const rejected = error => {
        try {
          if (!isFunction(rejectCb)) {
            rejectCbNext(error)
          } else {
            const result = rejectCb(error)
            if (result instanceof MyPromise) {
              result.then(resolveCbNext, rejectCbNext)
            } else {
              rejectCbNext(result)
            }
          }
        } catch (err) {
          rejectCbNext(err)
        }
      }
      // 状态变化之后执行
      switch (state) {
        case status.P:
          this.resolveCbQueue.push(fulfilled)        
          this.rejectCbQueue.push(rejected)
          break
        case status.F:
          fulfilled(value)
          break
        case status.R:
          rejected(reason)
          break
        default:
          break
      }
    })
  }
  catch (onRejected) {
    return this.then(undefined, onRejected)
  }
  // 无论什么情况，最后都需要执行
  finally (callback) {
    console.log('MyPromise finally')
    callback()
  }
}
const pp = new MyPromise((resolve, reject)=> {
  const flag = true
  setTimeout(()=> {
    if (flag) {
      resolve('pp setTimeout')
    } else {
      reject('pp reject')
    }
  }, 3000)
})
const mp = new MyPromise((resolve, reject)=> {
  const flag = false
  if (flag) {
    // 如果参数是一个promise的话，要等这个promise执行完那它的结果
    resolve(pp)
  } else {
    reject(new Error('reject error'))
  }
})
mp.then(res => {
  console.log('mp.then: ', res)
}
// ,err => {
//   console.log('mp.then: ', err)
// }
).catch(err => {
  console.log('mp.catch: ', err)
})
// ====== Promise ======
const objoo = { mid: '123456', token: '5234873' }
const datao = { a: 'aaa', b: { bb: 'bbb'} }
function paramsConsole ({ mid, token, type = 2 } = objoo, { a, b, b: { bb } } = datao) {
  console.log('params: ', mid, token, type, a, b, bb)
}
paramsConsole()
function restFun(...values) {
  console.log('函数参数：', values, arguments)
}
restFun(2, 4, 5)

/**
 * Set & Map
 */
const set1 = new Set()
set1.add(2).add(3)
console.log('set1.values: ', set1.values())
// while
let count = 0
while (count < 10) {
  console.log('count: ', count)
  count++
}
// do/while
let time = 10
do {
  console.log('time: ', time)
  time--
} while (time > 0)
// for
for(let i=0; i<10; i++) {
  console.log('for: ', i)
}
// for/of ES6
const forOf = [1,2,3,4,5,6,7,8,9,10]
let sum = 0
for(let v of forOf) {
  sum += v
}
console.log('for/of: ', sum)
const forSs = 'jhkjiugoanjasjasjgkljb'
const sumO = {}
for(let s of forSs) {
  if (sumO[s]) {
    sumO[s]++
  } else {
    sumO[s] = 1
  }
}
console.log('统计字符串字符出现次数：', sumO)
// for/in
const objIn = {
  a: 1,
  b: 2
}
for(let v in objIn) {
  console.log('forIn: ', objIn[v])
}
const o1 = { oo: 2, ob: 2, oa: 2 }
const o2 = { oo: 3, ob: 3, oc: 3 }
const o3 = { oo: 4, ob: 4, od: 4 }
Object.assign(o1, o2, o3) // 修改并返回第一个参数
console.log('assign: ', o1)

const point = {
  x: 10000,
  y: 200000000,
  toLocaleString: function () {
    return `${this.x.toLocaleString()}, ${this.y.toLocaleString()}`
  }
}
const point2 = {
  x: 3,
  y: 4,
  valueOf: function () {
    return Math.hypot(this.x, this.y)
  }
}
console.log('toLocaleString: ', point.toLocaleString())
console.log('valueOf: ', point > 4, point.valueOf(), point2 > 3, Number(point2))

const t = new Date()
console.log('toJSON: ', t.toJSON(), t.toLocaleString('chinese', { hour12: false }), t.toString())

const proxy1 = new Proxy({}, {
  get: function (target, proKey, receiver) {
    return receiver
  }
})
console.log('proxy1: ', proxy1.getReceiver === proxy1)

</script>
<style lang="less" scoped>
</style>
